﻿using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.Serialization;
using System.Text;
using System.Threading;

namespace OAuth2Authorizer.Framework
{
    internal class ResponseTypeBuilder
    {
        private TypeBuilder _builder;
        private List<PropertyBuilder> _properties = new List<PropertyBuilder>();
        internal Type ResponseType { get { return _builder.CreateType(); } }

        internal void BuildType(string typeName)
        {
            var assemblyName = new AssemblyName("dynamo");
            var assemblyBuilder = Thread.GetDomain().DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Run);
            var moduleBuilder = assemblyBuilder.DefineDynamicModule(assemblyBuilder.GetName().Name, false);
            _builder = moduleBuilder.DefineType(typeName,
                TypeAttributes.Public |
                TypeAttributes.Class |
                TypeAttributes.AutoClass |
                TypeAttributes.AnsiClass |
                TypeAttributes.BeforeFieldInit |
                TypeAttributes.AutoLayout,
                typeof(object),
                null);

            var constructorInfo = typeof(DataContractAttribute).GetConstructor(Type.EmptyTypes);
            var attributeBuilder = new CustomAttributeBuilder(constructorInfo, new object[] { });
            _builder.SetCustomAttribute(attributeBuilder);
        }

        internal void BuildProperty(string propertyName, string dataMemberName)
        {
            var fieldBuilder = _builder.DefineField("_" + propertyName,
                                                       typeof(string),
                                                       FieldAttributes.Private);

            var propBuilder = _builder.DefineProperty(propertyName,
                                                             PropertyAttributes.HasDefault,
                                                             typeof(string),
                                                             null);


            MethodAttributes getSetAttr = MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig;

            MethodBuilder propGetBuilder = _builder.DefineMethod("get_" + propertyName,
                                           getSetAttr,
                                           typeof(string),
                                           Type.EmptyTypes);

            var generator = propGetBuilder.GetILGenerator();
            generator.Emit(OpCodes.Ldarg_0);
            generator.Emit(OpCodes.Ldfld, fieldBuilder);
            generator.Emit(OpCodes.Ret);

            MethodBuilder propSetBuilder = _builder.DefineMethod("set_" + propertyName,
                                           getSetAttr,
                                           null,
                                           new Type[] { typeof(string) });

            generator = propSetBuilder.GetILGenerator();

            generator.Emit(OpCodes.Ldarg_0);
            generator.Emit(OpCodes.Ldarg_1);
            generator.Emit(OpCodes.Stfld, fieldBuilder);
            generator.Emit(OpCodes.Ret);

            propBuilder.SetGetMethod(propGetBuilder);
            propBuilder.SetSetMethod(propSetBuilder);

            var constructorInfo = typeof(DataMemberAttribute).GetConstructor(Type.EmptyTypes);
            var propertyInfo = typeof(DataMemberAttribute).GetProperty("Name");
            var propertyInfos = new PropertyInfo[] { propertyInfo };
            var propertyValues = new object[] { dataMemberName };
            var attributeBuilder = new CustomAttributeBuilder(constructorInfo, new object[] { }, propertyInfos, propertyValues);
            propBuilder.SetCustomAttribute(attributeBuilder);
            _properties.Add(propBuilder);
        }

        internal void BuildConverterMethod()
        {
            var methodBuilder = _builder.DefineMethod(
                                            "AsAccessTokenResponse",
                                            MethodAttributes.Public,
                                            typeof(AccessTokenResponse),
                                            Type.EmptyTypes);
            var generator = methodBuilder.GetILGenerator();
            generator.DeclareLocal(typeof(AccessTokenResponse));
            generator.Emit(OpCodes.Ldarg_0);
            generator.Emit(OpCodes.Call, _properties[0].GetGetMethod());
            generator.Emit(OpCodes.Ldarg_0);
            generator.Emit(OpCodes.Call, _properties[1].GetGetMethod());

            var accessTokenResponseConstructorInfo = typeof(AccessTokenResponse).GetConstructor(new Type[] { typeof(string), typeof(string) });
            generator.Emit(OpCodes.Newobj, accessTokenResponseConstructorInfo);
            generator.Emit(OpCodes.Stloc_0);

            if (_properties.Count > 2)
            {
                for (int i = 2; i < _properties.Count; i++)
                {
                    generator.Emit(OpCodes.Ldloc_0);
                    var getResponseTokensMethodInfo = typeof(AccessTokenResponse).GetMethod("get_ResponseTokens");
                    generator.Emit(OpCodes.Callvirt, getResponseTokensMethodInfo);
                    generator.Emit(OpCodes.Ldstr, _properties[i].Name);
                    generator.Emit(OpCodes.Ldarg_0);
                    generator.Emit(OpCodes.Call, _properties[i].GetGetMethod());
                    var addMethodInfo = typeof(NameValueCollection).GetMethod("Add", new Type[] { typeof(string), typeof(string) });
                    generator.Emit(OpCodes.Callvirt, addMethodInfo);
                }
            }

            generator.Emit(OpCodes.Ldloc_0);
            generator.Emit(OpCodes.Ret);
        }
    }
}